﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;

public static class libShell
{

    public static bool IsEditor
    {
        get
        {
            return Application.platform == RuntimePlatform.WindowsEditor ||
                Application.platform == RuntimePlatform.OSXEditor ||
                Application.platform == RuntimePlatform.LinuxEditor;
        }
    }
    public static bool IsMono
    {
        get
        {
            if (IsEditor)
                return true;
#if ENABLE_MONO 
            return true;
#else
            return false;
#endif
        }
    }
    public static IntPtr LoadModule(string filename)
    {
        if (IsEditor)
        {
            if (Application.platform == RuntimePlatform.WindowsEditor)
            {
                var handle = LoadLibrary(filename);
                return handle;
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        else
        {
            if (Application.platform == RuntimePlatform.WindowsPlayer)
            {
                var handle = LoadLibrary(filename);
                return handle;
            }
            else if (Application.platform == RuntimePlatform.Android)
            {
                var handle = dlopen(filename, 0);
                return handle;
            }
            else
            {
                throw new NotImplementedException();
            }
        }
    }
    public static IntPtr GetSymbol(IntPtr moduleHandle, string symbolname)
    {
        if (IsEditor)
        {
            if (Application.platform == RuntimePlatform.WindowsEditor)
            {
                return GetProcAddress(moduleHandle, symbolname);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        else
        {
            if (Application.platform == RuntimePlatform.WindowsPlayer)
            {
                return GetProcAddress(moduleHandle, symbolname);
            }
            else if (Application.platform == RuntimePlatform.Android)
            {
                return dlsym(moduleHandle, symbolname);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
    }
    public static IntPtr GetSymbolUnsafe(IntPtr moduleHandle, IntPtr symbolname)
    {
        if (IsEditor)
        {
            if (Application.platform == RuntimePlatform.WindowsEditor)
            {
                UnityEngine.Debug.LogWarning("GetSymbol EDITOR_WIN");
                return GetProcAddressUnsafe(moduleHandle, symbolname);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        else
        {
            if (Application.platform == RuntimePlatform.WindowsPlayer)
            {
                UnityEngine.Debug.LogWarning("GetSymbol standardalone_win");
                return GetProcAddressUnsafe(moduleHandle, symbolname);
            }
            else if (Application.platform == RuntimePlatform.Android)
            {
                return dlsymUnsafe(moduleHandle, symbolname);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
    }

    static IntPtr currentRuntimeModule;
    public static IntPtr GetRuntimeModule()
    {
        if (currentRuntimeModule == IntPtr.Zero)
        {
            if (IsEditor)
            {
                if (Application.platform == RuntimePlatform.WindowsEditor)
                {
                    var mods = System.Diagnostics.Process.GetCurrentProcess().Modules;
                    string modname = "";
                    foreach (System.Diagnostics.ProcessModule m in mods)
                    {
                        var name = m.ModuleName.ToLower();
                        if (name.Contains("mono-2.0.dll"))
                        {
                            modname = name;
                            currentRuntimeModule = m.BaseAddress;
                            break;
                        }

                        if (name.Contains("mono-2.0-bdwgc.dll"))
                        {
                            modname = name;
                            currentRuntimeModule = m.BaseAddress;
                            break;
                        }

                    }
                    UnityEngine.Debug.LogWarning("===Load Module " + modname);
                }
                else
                {
                    throw new NotImplementedException();
                }
            }
            else
            {
                if (Application.platform == RuntimePlatform.WindowsPlayer)
                {
                    currentRuntimeModule = libShell.GetModuleHandle("GameAssembly.dll");
                    UnityEngine.Debug.LogWarning("===GetCurrentModule" + currentRuntimeModule);
                }
                else if (Application.platform == RuntimePlatform.Android)
                {
                    if (IsMono)
                    {
                        //mono模式
                        currentRuntimeModule = libShell.dlopen("libmonobdwgc-2.0.so", 0);
                        if (currentRuntimeModule == IntPtr.Zero)
                        {
                            ILBridge.Log("load mono error.");
                        }
                    }
                    else
                    {
                        //il2cpp模式
                        currentRuntimeModule = libShell.dlopen("libil2cpp.so", 0);
                    }
                }
                else
                {
                    throw new NotImplementedException();
                }
            }
        }

        return currentRuntimeModule;


    }
    public static void FreeModule(IntPtr moduleHandle)
    {
        if (IsEditor)
        {
            if (Application.platform == RuntimePlatform.WindowsEditor)
            {
                UnityEngine.Debug.LogWarning("FreeModule EDITOR_WIN");
                FreeLibrary(moduleHandle);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        else
        {
            if (Application.platform == RuntimePlatform.WindowsPlayer)
            {
                UnityEngine.Debug.LogWarning("FreeModule standardalone_win");
                FreeLibrary(moduleHandle);
            }
            else if (Application.platform == RuntimePlatform.Android)
            {
                UnityEngine.Debug.LogWarning("FreeModule ANDROID");
                dlclose(moduleHandle);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
    }
    public static string GetLoadModuleError()
    {
        if (IsEditor)
        {
            if (Application.platform == RuntimePlatform.WindowsEditor)
            {
                var errid = (uint)Marshal.GetLastWin32Error();
                UnityEngine.Debug.LogError("errid=" + errid);
                if (errid == 0)
                    return "";
                IntPtr mem = Marshal.AllocHGlobal(1024);
                try
                {
                    //unity 下 FormatMessageA 始终乱码
                    var len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, IntPtr.Zero, errid, 0, mem, 1024, IntPtr.Zero);
                    if (len != 0)
                    {
                        var str = Marshal.PtrToStringUni(mem, (int)Math.Min(512, len));
                        return str;
                    }
                    return "unknown error:" + errid;
                }
                finally
                {
                    Marshal.FreeHGlobal(mem);
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        else
        {
            if (Application.platform == RuntimePlatform.WindowsPlayer)
            {
                var errid = (uint)Marshal.GetLastWin32Error();
                UnityEngine.Debug.LogError("errid=" + errid);
                if (errid == 0)
                    return "";
                IntPtr mem = Marshal.AllocHGlobal(1024);
                try
                {
                    //unity 下 FormatMessageA 始终乱码
                    var len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, IntPtr.Zero, errid, 0, mem, 1024, IntPtr.Zero);
                    if (len != 0)
                    {
                        var str = Marshal.PtrToStringUni(mem, (int)Math.Min(512, len));
                        return str;
                    }
                    return "unknown error:" + errid;
                }
                finally
                {
                    Marshal.FreeHGlobal(mem);
                }
            }
            else if (Application.platform == RuntimePlatform.Android)
            {
                IntPtr unsafeStr = libShell.dlerror();
                if (unsafeStr != IntPtr.Zero)
                {
                    return Marshal.PtrToStringAnsi(unsafeStr);
                }
                else
                {
                    return "cool";
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }

    }
    //#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN

    const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
    const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
    const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
    const uint FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
    const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
    const uint FORMAT_MESSAGE_FROM_STRING = 0x00000400;

    [DllImport("Kernel32.dll")]
    static extern uint GetLastError();
    [DllImport("Kernel32.dll", CharSet = CharSet.Ansi)]
    static extern uint FormatMessageW(
     uint dwFlags,
     IntPtr lpSource,
     uint dwMessageId,
     uint dwLanguageId,
     IntPtr lpBuffer,//这个lpbuffer 如果用FORMAT_MESSAGE_ALLOCATE_BUFFER 就是个char**，否则是个char*
     uint nSize,
     IntPtr Arguments);
    [DllImport("Kernel32.dll", SetLastError = true)]
    static extern IntPtr LoadLibrary(string lpFileName);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GetProcAddress(
        IntPtr hModule, string lpProcName);

    [DllImport("kernel32.dll", EntryPoint = "GetProcAddress", SetLastError = true)]
    static extern IntPtr GetProcAddressUnsafe(
    IntPtr hModule, IntPtr lpProcName);


    [DllImport("kernel32.dll", EntryPoint = "FreeLibrary", SetLastError = true)]
    static extern bool FreeLibrary(IntPtr hModule);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GetModuleHandle(string lpProcName);

    [DllImport("kernel32.dll", EntryPoint = "GetModuleHandle", SetLastError = true)]
    static extern IntPtr GetModuleHandleUnsafe(IntPtr lpProcName);
    //#endif
    //#if UNITY_ANDROID
    [DllImport("libdl.so", SetLastError = false)]
    public static extern IntPtr dlopen(string filename, int flag);

    [DllImport("libdl.so", SetLastError = false)]
    public static extern int dlclose(IntPtr handle);

    [DllImport("libdl.so", SetLastError = false)]
    public static extern IntPtr dlerror();

    [DllImport("libdl.so", SetLastError = false)]
    public static extern IntPtr dlsym(IntPtr handle, string functionName);

    [DllImport("libdl.so", EntryPoint = "dlsym", SetLastError = false)]
    public static extern IntPtr dlsymUnsafe(IntPtr handle, IntPtr functionName);

    //加载方法都在libdl.so中
    //[DllImport("libshell.so", EntryPoint = "dlOpen", SetLastError = false)]
    //public static extern IntPtr dlopen(string filename);

    //[DllImport("libshell.so", EntryPoint = "dlClose",SetLastError = false)]
    //public static extern int dlclose(IntPtr handle);

    //[DllImport("libshell.so", EntryPoint = "dlError",SetLastError = false)]
    //public static extern IntPtr dlerror();

    //[DllImport("libshell.so", EntryPoint = "dlSym",SetLastError = false)]
    //public static extern IntPtr dlsym(IntPtr handle, string functionName);

    //[DllImport("libshell.so", EntryPoint = "dlSym", SetLastError = false)]
    //public static extern IntPtr dlsymUnsafe(IntPtr handle, IntPtr functionName);


    [DllImport("libshell.so", EntryPoint = "dlAddrFName", SetLastError = false)]
    public static extern IntPtr dlAddrFName(IntPtr handle);

    [DllImport("libshell.so", EntryPoint = "dlOpenName", SetLastError = false)]
    public static extern IntPtr dlOpenName();
    //#endif
}
